package de.sendorian.app.forumArchive;

import java.io.IOException;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;

import de.sendorian.app.forumArchive.domain.Forum;
import de.sendorian.app.forumArchive.domain.Post;
import de.sendorian.app.forumArchive.domain.Topic;
import de.sendorian.app.forumArchive.download.Downloader;
import de.sendorian.app.forumArchive.finder.Finder;
import de.sendorian.app.forumArchive.repository.ForumRepository;


/**
 * Central class that manages the indexing and downloading.
 * 
 * @author sendorian
 * 
 */
@Component
public class ArchiveBuilder implements Runnable {

    private static Logger LOG = Logger.getLogger(ArchiveBuilder.class);

    private ForumRepository forumRepository;
    private Finder<String, Forum> forumFinder;
    private Finder<Forum, Topic> topicFinder;
    Finder<Topic, Post> postFinder;
    private String baseUrl;
    private Downloader downloader;
    private Compressor compressor;
    private Cleaner cleaner;


    public static void main(String[] args) throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
        String runBean;
        if (args != null && args.length > 0) {
            runBean = args[0];
        } else {
            runBean = "archiveBuilder";
        }
        Runnable mainClass = (Runnable) context.getBean(runBean);
        mainClass.run();
    }


    public void run() {
        List<Forum> forums = forumFinder.find(baseUrl);
        for (Forum forum : forums) {
            Forum persistenForum = forumRepository.findByName(forum.getName());
            if (persistenForum != null) {
                forum = persistenForum;
            }

            LOG.info("Finding topics for forum \"" + forum.getName() + "\"");
            List<Topic> topics = topicFinder.find(forum);

            for (Topic topic : topics) {
                if (hasToBeUpdated(forum, topic)) {
                    updateTopic(topic, forum);
                }
            }
            forumRepository.saveOrUpdate(forum);
        }

        forums = forumRepository.findAll();
        downloader.downloadForums(forums);
        compressor.compressImages(forums);
        cleaner.run();

        LOG.info("Finished!");
    }


    /**
     * Returns true if the topic is not yet persistent or outdated.
     * 
     */
    private boolean hasToBeUpdated(@Nonnull final Forum forum, @Nullable final Topic topic) {
        Predicate<Topic> uptodatePredicate = new Predicate<Topic>() {
            public boolean apply(Topic persistentTopic) {
                if (!persistentTopic.equals(topic))
                    return false;

                DateTime persistentLastChange = new DateTime(persistentTopic.getLastPost());
                DateTime onlineLastChange = new DateTime(topic.getLastPost());
                if (onlineLastChange.isAfter(persistentLastChange)) {
                    LOG.debug("Topic \"" + topic.getName() + "\" is outdated (DB: " + persistentLastChange
                            + ", Online: " + onlineLastChange + ").");
                    return false;
                }
                LOG.info("Topic \"" + topic.getName() + "\" is uptodate.");
                return true;
            }
        };
        return !Iterators.any(forum.getTopics().iterator(), uptodatePredicate);
    }


    /**
     * Updates the outdated Topic in the forum or adds it to the forums' topics
     * if it was not persisted before.
     * 
     */
    private void updateTopic(@Nonnull Topic topic, @Nonnull Forum forum) {
        LOG.info("Updating posts for topic \"" + topic.getName() + "\"");
        List<Post> posts = postFinder.find(topic);
        topic.setPosts(posts);
        forum.getTopics().remove(topic);
        forum.getTopics().add(topic);
    }


    @Value("#{config.baseUrl}")
    public void setBaseUrl(String baseUrl) {
        this.baseUrl = baseUrl;
    }


    @Autowired
    public void setForumRepository(ForumRepository forumRepository) {
        this.forumRepository = forumRepository;
    }


    @Value("#{config.forumFinder}")
    public void setForumFinder(Finder<String, Forum> forumFinder) {
        this.forumFinder = forumFinder;
    }


    @Value("#{config.topicFinder}")
    public void setTopicFinder(Finder<Forum, Topic> topicFinder) {
        this.topicFinder = topicFinder;
    }


    @Value("#{config.postFinder}")
    public void setPostFinder(Finder<Topic, Post> postFinder) {
        this.postFinder = postFinder;
    }


    @Autowired
    public void setDownloader(Downloader downloader) {
        this.downloader = downloader;
    }


    @Autowired
    public void setCompressor(Compressor compressor) {
        this.compressor = compressor;
    }


    @Autowired
    public void setCleaner(Cleaner cleaner) {
        this.cleaner = cleaner;
    }
}
